﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Diagnostics;

namespace ActiveSharp.PropertyMapping
{
    /// <summary>
    /// A faster alterative to the orginal public API (which was <see cref="PropertyMapForType"/> by way of
    /// <see cref="PropertyMap"/>
    /// </summary>
    /// <remarks>
    /// Avoids all three of the dictionary lookups that used to happen with the old API.
    /// (The first was in <see cref="PropertyMap"/>, and is effectively replaced here by our static-constructor-on-generic-type)
    ///  The second and third were in <see cref="PropertyMap.GetProperty"/> and are replaced here by out array indexed by offset)
    /// </remarks>
    /// <typeparam name="TType">The type of object for which this is a property map</typeparam>
    public class FastPropertyMap<TType>
    {
        /// <summary>
        /// Gets the basic mapping info, then expands it out (from the compact Dictionary-based format
        /// in which we receive it) to an array (with lots of empty space, but faster lookup)
        /// </summary>
        static string[] CreateArray()
        {
            OffsetToFieldMap offsetsToFields = new OffsetToFieldMap(typeof(TType));
            FieldToPropertyMap fieldsToProperties = new FieldToPropertyMap(typeof(TType));

            int maxOffset = offsetsToFields.MaxOffset;
            string [] result = new string[maxOffset + 1];  // + 1 since its a count, not the max index value
            for (int i = 0; i <= maxOffset; i++)
            {
                string fieldName = offsetsToFields.GetFieldOrNull(i);
                string propName;
                if (fieldName == null)
                {
                    propName = null;
                }
                else
                {
                    PropertyInfo prop = fieldsToProperties.GetPropertyInfoOrNull(fieldName);
                    propName = prop == null ? null : prop.Name;
                }
                result[i] = propName;
            }
            return result;
        }

        static readonly string[] _propNamesByOffset = CreateArray();  // MUST do like this, rather than explict static constructor (since explict cons changes before field init, and makes things, overall, about 20 times slower in my testing!!!)

        /// <summary>
        /// Gets the name of the property that corresponds to <paramref name="field"/>
        /// </summary>
        /// <remarks>Note that <paramref name="containingObject"/> MUST be the instance that contains
        /// <paramref name="field"/>.  You will get exceptions or meaningless results if it is NOT that instance.
        /// Also not that <paramref name="containingObject"/> must be of type <typeparamref name="TType"/> EXACTLY (at RUNTIME,
        /// not just compile time). You will get array bounds errors if it is a subclass of TType at runtime.</remarks>
        public static string GetPropertyName<T>(TType containingObject, ref T field) 
        {
            int offset = OffsetUtil.GetOffset(containingObject, ref field);
            return _propNamesByOffset[offset];
        }

    }

}
